package modelo;

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;

public class Juego {
	
	public static String archivoCiudadesPistas = "ciudadespistas.xml";
	public static String archivoSospechosos = "sospechosos.xml";
	public static String archivoJugadores = "jugadores.xml";
	private String archivoObjetos = "objetosRobados.xml";
	private static Juego instancia;
	private List<Ciudad> ciudades;
	private List<Ladron> sospechosos;
	private Ladron ladron;
	private List<Pista> pistas;
	private Policia policia;
	private boolean terminoJuego = false;
	private boolean victoria = false;
	private ArrayList<ObjetoRobado> objetosRobados;
	
	
	public void setSospechosos(List<Ladron> sospechosos) {
		this.sospechosos = sospechosos;
	}

	public void setLadron(Ladron ladron) {
		this.ladron = ladron;
	}
	
	
	public Ladron getLadron() {
		return ladron;
	}

	private Juego(){
		cargarXMLs();
	}

	public void inicializarMundo() {
		cargarXMLs();
		
		terminoJuego = false;
		victoria = false;
		
		agregarEdificiosACiudades();
		generarLadron();
		cargarPistasACiudades();
		agregarConexionesACiudades();
		
		this.policia.reiniciarParaProximoCaso();
		this.policia.setCiudadActual(this.ladron.getRuta().get(0));
	}

	private void generarLadron() {
		//Ladron: Al azar desde la lista de sospechosos
		int random = (int) (Math.random() * sospechosos.size());
		ladron = sospechosos.get(random);
		
		//Ruta: Al azar
		while(ladron.getRuta().size() < this.policia.getRango().getCantidadCiudadesCaso()){
			random = (int) (Math.random() * ciudades.size());
			if(!ladron.ruta.contains(ciudades.get(random))){
				ladron.ruta.add(ciudades.get(random));
			}
		}
		
		//ObjetoRobado: Al azar
		List<ObjetoRobado> objetos = obtenerObjetosRobadosRango();
		int i = (int)(Math.random() * objetos.size());
		this.ladron.setObjeto(objetos.get(i));
		
		//Seteo al azar el edificio donde esta escondido el ladron
		random = (int) (Math.random() * 3);
		ladron.setEdificio(ladron.getRuta().get(ladron.getRuta().size()-1).getEdificios().get(random));
	}

	private List<ObjetoRobado> obtenerObjetosRobadosRango() {
		List<ObjetoRobado> objetos = new ArrayList<ObjetoRobado>();
		for(ObjetoRobado obj : objetosRobados){
			if(obj.getTipo().equals(this.policia.getRango().getTipoObjeto())){
				objetos.add(obj);
			}
		}
		return objetos;
	}

	private void cargarPistasACiudades() {
		PistaAtaque.reiniciar();
		//Genero las pistas sobre los rasgos del ladron
		ArrayList<Pista> pistasRasgos = generarPistasRasgos();
		
		//Cargo las pistas para las ciudades de la ruta
		for (int i = 0; i < ladron.getRuta().size()-1; i++){
			//Comunes
			ladron.getRuta().get(i).cargarPistas( getPistasForCiudad( ladron.getRuta().get(i+1) ) );
			
			//Rasgos
			int random = (int) (Math.random() * pistasRasgos.size());
			Pista pistaRasgo = pistasRasgos.get(random);
			ladron.getRuta().get(i).agregarPistaRasgo(pistaRasgo);
			pistasRasgos.remove(pistaRasgo);
		}
		
		//Cargo pistas en la ciudad donde se esconde el ladron
		List<Pista> pistasAqui = generarPistasAtaque();
		ladron.getRuta().get(ladron.getRuta().size()-1).cargarPistas(pistasAqui);
		
		List<Ciudad> ciudadesAux = obtenerCiudadesQueNoEstanEnLaRuta();
		
		//Cargo pistas en las ciudades que no estan en la ruta
		List<Pista> pistasDesviado = generarPistasDesviado();
		for (Ciudad c : ciudadesAux){
			c.cargarPistas(pistasDesviado);
		}
		
		//Agrego pistas al edificio donde se esconde el ladron
		cargarPistasAEdificioLadron();
	}

	private void cargarPistasAEdificioLadron() {
		List<Pista> pistasUltimoEdificio = new ArrayList<Pista>();
		RubroPista rubro = this.ladron.getEdificio().getMyRubro();
		
		pistasUltimoEdificio.add(new Pista(TipoPista.Dificil, null, "Se escuchan pasos en la oscuridad...", rubro));
		pistasUltimoEdificio.add(new Pista(TipoPista.Media, null, "Se escuchan pasos en la oscuridad...", rubro));
		pistasUltimoEdificio.add(new Pista(TipoPista.Facil, null, "Se escuchan pasos en la oscuridad...", rubro));
		
		this.ladron.getEdificio().getPistas().clear();
		this.ladron.getEdificio().cargarPistas(pistasUltimoEdificio);
	}

	private void agregarConexionesACiudades() {
		
		List<Ciudad> ciudadesNoEnRuta = obtenerCiudadesQueNoEstanEnLaRuta();
		
		for (int i = 0; i < ladron.getRuta().size(); i++){
			
			Ciudad c = ladron.getRuta().get(i);
			c.getConexiones().clear();
			
			//Anterior y siguiente
			if(i>0){
				c.getConexiones().add(ladron.getRuta().get(i-1));
			}
			if(i < ladron.getRuta().size() - 1){
				c.getConexiones().add(ladron.getRuta().get(i+1));
			}
			
			//Se agrega conexion de vuelta a la ciudad de la ruta
			while(c.getConexiones().size() < 4){
				int cityIndex = (int) (Math.random() * ciudadesNoEnRuta.size());
				if(!c.getConexiones().contains(ciudadesNoEnRuta.get(cityIndex))){
					c.getConexiones().add(ciudadesNoEnRuta.get(cityIndex));
					ciudadesNoEnRuta.get(cityIndex).getConexiones().add(c);
				}
			}
		}
		
		//Agrego conexiones a todas las ciudades que no estan en la ruta
		for (Ciudad c : ciudadesNoEnRuta){
			while(c.getConexiones().size() < 4){
				int cityIndex;
				cityIndex = (int) (Math.random() * ciudadesNoEnRuta.size());
				if(!c.getConexiones().contains(ciudadesNoEnRuta.get(cityIndex))){
					c.getConexiones().add(ciudadesNoEnRuta.get(cityIndex));
				}
			}
		}
		
		//Randomizo el orden de las conexiones para que la correcta no sea siempre la segunda
		for(Ciudad c : ciudades){
			Collections.shuffle(c.getConexiones());
		}
	}

	private List<Ciudad> obtenerCiudadesQueNoEstanEnLaRuta() {
		List<Ciudad> ciudadesAux = new ArrayList<Ciudad>();
		for(Ciudad c : ciudades){
			ciudadesAux.add(c);
		}
		ciudadesAux.removeAll(ladron.getRuta());
		return ciudadesAux;
	}

	private List<Pista> generarPistasDesviado() {
		List<Pista> pistasDesviado = new ArrayList<Pista>();
		pistasDesviado.add(new Pista(TipoPista.Facil, null, "Nunca vi a ese sujeto", RubroPista.Economia));
		pistasDesviado.add(new Pista(TipoPista.Facil, null, "No se de que estas hablando", RubroPista.Geografia));
		pistasDesviado.add(new Pista(TipoPista.Facil, null, "Creo que te has equivocado", RubroPista.Historia));
		pistasDesviado.add(new Pista(TipoPista.Media, null, "Nunca vi a ese sujeto", RubroPista.Economia));
		pistasDesviado.add(new Pista(TipoPista.Media, null, "No se de que estas hablando", RubroPista.Geografia));
		pistasDesviado.add(new Pista(TipoPista.Media, null, "Creo que te has equivocado", RubroPista.Historia));
		pistasDesviado.add(new Pista(TipoPista.Dificil, null, "Nunca vi a ese sujeto", RubroPista.Economia));
		pistasDesviado.add(new Pista(TipoPista.Dificil, null, "No se de que estas hablando", RubroPista.Geografia));
		pistasDesviado.add(new Pista(TipoPista.Dificil, null, "Creo que te has equivocado", RubroPista.Historia));
		return pistasDesviado;
	}

	private void agregarEdificiosACiudades() {
		for(Ciudad c : ciudades){
			c.getEdificios().clear();

			if(Math.random() > 0.5){
				c.agregarEdificio(new Edificio(TipoEdificio.Bolsa));
			}
			else{
				c.agregarEdificio(new Edificio(TipoEdificio.Banco));
			}
			
			if(Math.random() > 0.5){
				c.agregarEdificio(new Edificio(TipoEdificio.Aeropuerto));
			}
			else{
				c.agregarEdificio(new Edificio(TipoEdificio.Puerto));
			}
			
			c.agregarEdificio(new Edificio(TipoEdificio.Biblioteca));
			
			
			Collections.shuffle(c.getEdificios());
		}
	}

	private List<Pista> generarPistasAtaque() {
		List<Pista> pistasAqui = new ArrayList<Pista>();
		pistasAqui.add(new PistaAtaque(TipoPista.Facil, null, "Escuche que se esconde cerca de aqui", RubroPista.Economia));
		pistasAqui.add(new PistaAtaque(TipoPista.Facil, null, "Vi a alguien parecido", RubroPista.Geografia));
		pistasAqui.add(new PistaAtaque(TipoPista.Facil, null, "Lo he visto en la ciudad", RubroPista.Historia));
		pistasAqui.add(new PistaAtaque(TipoPista.Media, null, "Escuche que se esconde cerca de aqui", RubroPista.Economia));
		pistasAqui.add(new PistaAtaque(TipoPista.Media, null, "Vi a alguien parecido", RubroPista.Geografia));
		pistasAqui.add(new PistaAtaque(TipoPista.Media, null, "Lo he visto en la ciudad", RubroPista.Historia));
		pistasAqui.add(new PistaAtaque(TipoPista.Dificil, null, "Escuche que se esconde cerca de aqui", RubroPista.Economia));
		pistasAqui.add(new PistaAtaque(TipoPista.Dificil, null, "Vi a alguien parecido", RubroPista.Geografia));
		pistasAqui.add(new PistaAtaque(TipoPista.Dificil, null, "Lo he visto en la ciudad", RubroPista.Historia));
		return pistasAqui;
	}

	private ArrayList<Pista> generarPistasRasgos() {
		ArrayList<Pista> pistasRasgos = new ArrayList<Pista>();
		pistasRasgos.add(new Pista(TipoPista.Rasgo, "Era " + ladron.sexo.toString()));
		pistasRasgos.add(new Pista(TipoPista.Rasgo, "Su cabello era " + ladron.cabello.toString()));
		pistasRasgos.add(new Pista(TipoPista.Rasgo, "Su hobby era " + ladron.hobby.toString()));
		pistasRasgos.add(new Pista(TipoPista.Rasgo, "Llevaba " + ladron.senia.toString()));
		pistasRasgos.add(new Pista(TipoPista.Rasgo, "Conducia un " + ladron.vehiculo.toString()));
		return pistasRasgos;
	}
	
	private void cargarXMLs()
	{
		cargarCiudadesPistasXML();
		cargarSospechosos();
		cargarObjetosRobados();
	}

private void cargarSospechosos() {
		
		sospechosos = new ArrayList<Ladron>();
		try{
			Document doc = abrirXml(archivoSospechosos);
			
			NodeList nList = doc.getElementsByTagName("lista").item(0).getChildNodes();
			
			for (int temp = 0; temp < nList.getLength(); temp++){
				Node nNode = nList.item(temp);
				if (nNode.getNodeType() == Node.ELEMENT_NODE){
					Element eElement = (Element) nNode;
					sospechosos.add(new Ladron(eElement));
				}
			}	
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}

	private void cargarObjetosRobados() {
		
		objetosRobados = new ArrayList<ObjetoRobado>();
		try{
			Document doc = abrirXml(archivoObjetos);
			
			NodeList nList = doc.getElementsByTagName("objetos").item(0).getChildNodes();
			
			for (int temp = 0; temp < nList.getLength(); temp++){
				Node nNode = nList.item(temp);
				if (nNode.getNodeType() == Node.ELEMENT_NODE){
					Element eElement = (Element) nNode;
					objetosRobados.add(new ObjetoRobado(eElement.getAttribute("nombre"), TipoObjeto.valueOf(eElement.getAttribute("tipo"))));
				}
			}	
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}

	private void cargarCiudadesPistasXML() {
		ciudades = new ArrayList<Ciudad>();
		pistas = new ArrayList<Pista>();
		
		try{
			Document doc = abrirXml(archivoCiudadesPistas);
			
			NodeList nList = doc.getElementsByTagName("lista").item(0).getChildNodes();
			
			for (int temp = 0; temp < nList.getLength(); temp++){
				Node nNode = nList.item(temp);
				
				if (nNode.getNodeType() == Node.ELEMENT_NODE){
					Element eElement = (Element) nNode;
					Ciudad ciudad = new Ciudad(eElement);
					ciudades.add(ciudad);
					
					NodeList nListInterior = nNode.getChildNodes();
					for (int i = 0; i < nListInterior.getLength(); i++){
						Node nNodeInterior = nListInterior.item(i);
						
						if (nNodeInterior.getNodeType() == Node.ELEMENT_NODE){
							Element eElementInterior = (Element) nNodeInterior;
							pistas.add(new Pista(eElementInterior, ciudad));
						}
					}
				}
			}
		}
		catch (Exception e){
			e.printStackTrace();
		}
	}

	private Document abrirXml(String ruta){
		
		Document doc = null;
		
		try{
			File fXmlFile = new File(ruta);
			DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder dBuilder;
			dBuilder = dbFactory.newDocumentBuilder();
			doc = dBuilder.parse(fXmlFile);
			doc.getDocumentElement().normalize();
		}
		catch(Exception e){
			e.printStackTrace();
		}
		
		return doc;
	}
	
	private List<Pista> getPistasForCiudad(Ciudad ciudad)
	{
		List<Pista> aux = new ArrayList<Pista>();
		
		for(Pista p : pistas){
			if(p.getCiudadPista() == ciudad){
				aux.add(p);
			}
		}
		
		return aux;
	}
	
	public static void reestablecer(){
		archivoCiudadesPistas = "ciudadespistas.xml";
		instancia = null;
	}
	
	public static Juego getInstancia(){
		if(instancia == null)
			instancia = new Juego();
		
		return instancia;
	}
	
	public List<Ladron> getSospechosos(){
		return this.sospechosos;
	}

	public Policia getPolicia() {
		return this.policia;
	}
	
	public boolean haGanado()
	{
		return terminoJuego && victoria;
	}
	
	public boolean haTerminado()
	{
		return terminoJuego;
	}
	
	public void terminar(boolean victoria){
		this.terminoJuego = true;
		this.victoria = victoria;
	}
	
	public void cargarJugadorPorNombre(String nombre){
		int arrestosRealizados = 0;
		
		Document doc = abrirXml(archivoJugadores);
		
		NodeList nList = doc.getElementsByTagName("jugadores").item(0).getChildNodes();
		
		for (int temp = 0; temp < nList.getLength(); temp++){
			Node nNode = nList.item(temp);
			
			if (nNode.getNodeType() == Node.ELEMENT_NODE){
				Element eElement = (Element)nNode;
				
				if(eElement.getAttribute("nombre").equals(nombre)){
					arrestosRealizados = Integer.parseInt(eElement.getAttribute("arrestos"));
					break;
				}
			}
		}
		
		this.policia = new Policia();
		this.policia.setNombre(nombre);
		for (int i = 0; i < arrestosRealizados; i++) {
			this.policia.sumarArresto();
		}

		this.inicializarMundo();
	}
	
	public void guardarJugadorActualEnXml(){
		
		String nombre = this.getPolicia().getNombre();
		String arrestos = this.getPolicia().getArrestosRealizados() + "";
		
		Document doc = abrirXml(archivoJugadores);
		
		NodeList nList = doc.getElementsByTagName("jugadores").item(0).getChildNodes();
		
		//Borro el jugador si ya existe
		for (int temp = 0; temp < nList.getLength(); temp++){
			Node nNode = nList.item(temp);
			
			if (nNode.getNodeType() == Node.ELEMENT_NODE){
				Element eElement = (Element)nNode;
				
				if(eElement.getAttribute("nombre").equals(nombre)){
					doc.getElementsByTagName("jugadores").item(0).removeChild(nNode);
					break;
				}
			}
		}
		
		//Agrego el jugador con los datos actualizados
		Element jugador = doc.createElement("jugador");
		jugador.setAttribute("nombre", nombre);
		jugador.setAttribute("arrestos", arrestos);
	    doc.getDocumentElement().appendChild(jugador);
		
		agregarContenidoXml(archivoJugadores, doc);
	}

	private void agregarContenidoXml(String ruta, Document doc)
			throws TransformerFactoryConfigurationError {
		try {
			TransformerFactory tFactory = TransformerFactory.newInstance();
	        Transformer transformer = tFactory.newTransformer();    
	        transformer.setOutputProperty(OutputKeys.INDENT,"yes");
	
	        DOMSource source = new DOMSource(doc);
	        StreamResult result = new StreamResult(new FileOutputStream(ruta));
	        transformer.transform(source, result);
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}
}